/**
   main.cpp - Created by Timothy Morey on 8/9/11.
 */

#include <iostream>
#include <list>
#include <math.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <string>
using namespace std;

#include <boost/algorithm/string.hpp>
using namespace boost;

#include "glut_display.h"
#include "simulator.h"
#include "../common/vis_server.h"

#define DISP_SIZE 512

bool g_LocalDisplay = false;
bool g_KeepListening = true;

void Print(list<string> args);
void SetParameters(list<string> args);
void* DisplayExportLoop(void* arg);

void ParseCLArgs(int argc, char* argv[], unsigned short& port,
		 unsigned short& width, unsigned short& height,
		 bool& localDisplay);
list<string> ParseArgs(const string& input);
string ParseCommand(const string& input);

int main(int argc, char* argv[])
{
  unsigned short port = 12345;
  unsigned short width = 640;
  unsigned short height = 480;
  ParseCLArgs(argc, argv, port, width, height, g_LocalDisplay);

  if(g_LocalDisplay)
    GlutDisplay::Instance().Init(0, 0, width, height, "Particle Simulator");

  VisServer* vis = new VisServer(port);
  Simulator::Instance().SetVisServer(vis);
  Simulator::Instance().SetFrameSize(width, height);

  while(1)
  {
    string input;

    cout << ")))> ";
    getline(cin, input);
    string cmd = ParseCommand(input);

    if(cmd == "q" ||
       cmd == "quit")
    {
      g_KeepListening = false;
      break;
    }
    else if(cmd == "start")
    {
      Simulator::Instance().Start();
    }
    else if(cmd == "stop")
    {
      Simulator::Instance().Stop();
    }
    else if(cmd == "s")
    {
      if(Simulator::Instance().IsRunning())
	Simulator::Instance().Stop();
      else
	Simulator::Instance().Start();
    }
    else if(cmd == "pause" ||
	    cmd == "p")
    {
      Simulator::Instance().Pause();
    }
    else if(cmd == "resume")
    {
      Simulator::Instance().Resume();
    }
    else if(cmd == "set")
    {
      list<string> args = ParseArgs(input);
      SetParameters(args);
    }
    else if(cmd == "print")
    {
      list<string> args = ParseArgs(input);
      Print(args);
    }
    else if(cmd == "rand")
    {
      double magnitude = 1.0;
      list<string> args = ParseArgs(input);
      if(args.size() > 0)
	magnitude = atof(args.front().c_str());

      Simulator::Instance().RandomizePositions();
      Simulator::Instance().RandomizeVelocities(magnitude);
    }
    else if(cmd == "rand-pos")
    {
      Simulator::Instance().RandomizePositions();
    }
    else if(cmd == "rand-vel")
    {
      double magnitude = 1.0;
      list<string> args = ParseArgs(input);
      if(args.size() > 0)
	magnitude = atof(args.front().c_str());

      Simulator::Instance().RandomizeVelocities(magnitude);
    }
    else
    {
      printf("Unrecognized command: \"%s\"\n", cmd.c_str());
    }
  }

  if(g_LocalDisplay)
    GlutDisplay::DestroyInstance();

  Simulator::DestroyInstance();

  return 0;
}

void Print(list<string> args)
{
  if(args.size() == 0)
  {
    Simulator::Instance().PrintParticles();
  }
  else
  {
    const Parameters & parms = Simulator::Instance().GetParameters();

    list<string>::const_iterator iter;
    for(iter = args.begin(); iter != args.end(); iter++)
    {
      if(*iter == "parts")
      {
        Simulator::Instance().PrintParticles();
      }
      if(*iter == "width" ||
         *iter == "w")
      {
        printf("world-width = %f\n", parms.MaxX - parms.MinX);
      }
      else if(*iter == "height" ||
              *iter == "h")
      {
        printf("world-height = %f\n", parms.MaxY - parms.MinY);
      }
      else if(*iter == "n")
      {
        printf("n = %d\n", parms.N);
      }
      else if(*iter == "dt" ||
              *iter == "step" ||
              *iter == "step-size")
      {
        printf("dt = %f\n", parms.dt);
      }
      else if(*iter == "time")
      {
        Simulator::Instance().PrintStepRateReport();

        if(g_LocalDisplay)
          GlutDisplay::Instance().PrintFrameRateReport();
      }
      else
      {
        printf("Error: unrecognized parameter name: \"%s\"", (*iter).c_str());
      }
    }
  }
}

void SetParameters(list<string> args)
{
  Parameters parms = Simulator::Instance().GetParameters();
  bool updateParms = false;

  list<string>::iterator iter;
  for(iter = args.begin(); iter != args.end(); iter++)
  {
    list<string> chunks;
    split(chunks, *iter, is_any_of("=:"), token_compress_on);

    if(chunks.size() == 2)
    {
      string name = chunks.front();
      to_lower(name);
      float val = atof(chunks.back().c_str());

      if(name == "width" ||
	 name == "w")
      {
	parms.MaxX = parms.MinX + val;
	updateParms = true;
      }
      else if(name == "height" ||
	      name == "h")
      {
	parms.MaxY = parms.MinY + val;
	updateParms = true;
      }
      else if(name == "n")
      {
	parms.N = (int)floor(val);
	updateParms = true;
      }
      else if(name == "dt" ||
	      name == "step" ||
	      name == "step-size")
      {
	Simulator::Instance().SetStepSize(val);
	parms.dt = val;
      }
      else
      {
	printf("Error: unrecognized parameter name: \"%s\"", name.c_str());
      }
    }
    else
    {
      printf("Error: unable to parse \"%s\"", (*iter).c_str());
    }
  }

  if(updateParms)
  {
    bool success = Simulator::Instance().UpdateParameters(parms);

    if(! success)
    {
      printf("An error was encountered while setting parameters.\n");
      printf("Perhas you specified a value of N that is too large for the given space.\n");
    }
  }
}

void ParseCLArgs(int argc, char* argv[], unsigned short& port, 
		 unsigned short& width, unsigned short& height,
		 bool& localDisplay)
{
  for(int i = 1; i < argc; i++)
  {
    if(i + 1 < argc &&
       (0 == strcmp(argv[i], "-p") ||
	0 == strcmp(argv[i], "-port")))
    {
      port = atoi(argv[i+1]);
    }
    else if(i + 1 < argc &&
	    (0 == strcmp(argv[i], "-w") ||
	     0 == strcmp(argv[i], "-width")))
    {
      width = atoi(argv[i+1]);
    }
    else if(i + 1 < argc &&
	    (0 == strcmp(argv[i], "-h") ||
	     0 == strcmp(argv[i], "-height")))
    {
      height = atoi(argv[i+1]);
    }
    else if(i + 2 < argc &&
	    (0 == strcmp(argv[i], "-d") ||
	     0 == strcmp(argv[i], "-dim")))
    {
      width = atoi(argv[i+1]);
      height = atoi(argv[i+2]);
    }
    else if(0 == strcmp(argv[i], "-l") ||
	    0 == strcmp(argv[i], "-local"))
    {
      localDisplay = true;
    }
  }

  printf("Port: %d\n", port);
  printf("Width: %d\n", width);
  printf("Height: %d\n", height);
  printf("Local Display: %s\n", localDisplay ? "true" : "false");
}

list<string> ParseArgs(const string& input)
{
  list<string> retval;

  split(retval, input, is_any_of(" "), token_compress_on);
  retval.pop_front();

  return retval;
}

string ParseCommand(const string& input)
{
  string retval;

  list<string> chunks;
  split(chunks, input, is_any_of(" "), token_compress_on);

  if(chunks.size() > 0)
    retval = chunks.front();

  return retval;
}

void* DisplayExportLoop(void* arg)
{
  sockaddr_in serverAddress;
  bzero(&serverAddress, sizeof(serverAddress));
  serverAddress.sin_family = AF_INET;
  serverAddress.sin_port = htons(12345);
  serverAddress.sin_addr.s_addr = INADDR_ANY;

  int socketFD = socket(AF_INET, SOCK_STREAM, 0);
  if(socketFD < 0)
    perror("Error openning socket");

  if(bind(socketFD, (sockaddr*)&serverAddress, sizeof(serverAddress)) < 0)
    perror("Error binding socket");

  if(listen(socketFD, 5) < 0)
    perror("Error listening to socket");

  while(g_KeepListening)
  {
    sockaddr_in clientAddress;
    socklen_t clientLen = sizeof(clientAddress);
    int newSocketFD = accept(socketFD, (sockaddr*)&clientAddress, &clientLen);
    if(newSocketFD < 0)
      perror("Error accepting connection");

    while(true)
    {
      char incoming[256];
      bzero(incoming, 256);
      int n = read(newSocketFD, incoming, 255);
      
      if(strcmp(incoming, "get-parts") == 0)
      {
        Simulator& sim = Simulator::Instance();
        //printf("writing %d parts...\n", sim.GetParameters().N);
        size_t len = sizeof(int) + sizeof(float)*sim.GetParameters().N*2;
        char* data = new char[len];
        ((int*)data)[0] = sim.GetParameters().N;
        for(int i = 0; i < sim.GetParameters().N; i++)
        {
          ((float*)(data+sizeof(int)))[2*i] = (float)sim.GetX(i);
          ((float*)(data+sizeof(int)))[2*i+1] = (float)sim.GetY(i);
        }
        
        size_t written = 0;
        while(written < len)
        {
          n = write(newSocketFD, ((char*)data)+written, len - written);
          written += n;
          //printf("wrote %d bytes, %d bytes left\n ", n, len - written);
        }
        
        delete data;
      }
      else if(strcmp(incoming, "get-frame") == 0)
      {
        //printf("sending frame...\n");
        Simulator& sim = Simulator::Instance();
        size_t len = sizeof(int) + sizeof(int) + 
          sizeof(unsigned char) * sim.GetFrameWidth() * sim.GetFrameHeight() * 4;
        char* data = new char[len];
        ((int*)data)[0] = sim.GetFrameWidth();
        ((int*)data)[1] = sim.GetFrameHeight();
        memcpy(data+(2*sizeof(int)), sim.GetFrame(), len - 2*sizeof(int));

        size_t written = 0;
        while(written < len)
        {
          n = write(newSocketFD, ((char*)data)+written, len - written);
          //printf("wrote %d\n", n);
          written += n;
        }
        //printf("sent frame\n");
        delete data;
      }
      else if(strcmp(incoming, "get-bounds") == 0)
      {
        Simulator& sim = Simulator::Instance();
        float* data = new float[4];
        data[0] = sim.GetParameters().MinX;
        data[1] = sim.GetParameters().MaxX;
        data[2] = sim.GetParameters().MinY;
        data[3] = sim.GetParameters().MaxY;
        size_t len = sizeof(float) * 4;
        size_t written = 0;
        while(written < len)
        {
          n = write(newSocketFD, ((char*)data)+written, len - written);
          written += n;
        }
        
        delete data;
      }
      else if(strcmp(incoming, "start-sim") == 0)
      {
        Simulator::Instance().Start();
      }
      else if(strcmp(incoming, "stop-sim") == 0)
      {
        Simulator::Instance().Stop();
      }
      else if(strcmp(incoming, "close-session") == 0)
      {
        break;
      }
    }
  }
}
